package com.shiehub.sdk.opengw.encrypt.SM2Util;

/**
 * @Description: SM3
 * @Author: guoleijie@shie.com.cn
 * @Since:2016年9月13日 17:25:00
 */
public class SM3 {
  /*
   * public static final byte[] iv = { 0x2C, (byte) 0x91, (byte) 0xB4, 0x01,
   * (byte) 0xFC, 0x64, (byte) 0xB2, (byte) 0xCE, 0x7C, 0x4E, (byte) 0xAE,
   * (byte) 0xFB, (byte) 0xB1, 0x3B, (byte) 0xB6, (byte) 0xD3, 0x17, 0x60,
   * (byte) 0xB6, 0x35, (byte) 0xF3, 0x6F, 0x13, (byte) 0xEB, (byte) 0xC8,
   * 0x77, (byte) 0xE9, (byte) 0xA0, (byte) 0xC2, 0x76, (byte) 0xA8, 0x17 };
   */

  public static final byte[] iv = {0x73, (byte) 0x80, 0x16, 0x6f, 0x49,
      0x14, (byte) 0xb2, (byte) 0xb9, 0x17, 0x24, 0x42, (byte) 0xd7,
      (byte) 0xda, (byte) 0x8a, 0x06, 0x00, (byte) 0xa9, 0x6f, 0x30,
      (byte) 0xbc, (byte) 0x16, 0x31, 0x38, (byte) 0xaa, (byte) 0xe3,
      (byte) 0x8d, (byte) 0xee, 0x4d, (byte) 0xb0, (byte) 0xfb, 0x0e,
      0x4e};

  public static int[] Tj = new int[64];

  static {
    for (int i = 0; i < 16; i++) {
      Tj[i] = 0x79cc4519;
    }

    for (int i = 16; i < 64; i++) {
      Tj[i] = 0x7a879d8a;
    }
  }

  /**
   * @Description:
   * @ReturnType：byte[]
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  public static byte[] CF(byte[] V, byte[] B) {
    int[] v, b;
    v = convert(V);
    b = convert(B);
    return convert(CF(v, b));
  }

  /**
   * @Description:
   * @ReturnType：int[]
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  private static int[] convert(byte[] arr) {
    int[] out = new int[arr.length / 4];
    byte[] tmp = new byte[4];
    for (int i = 0; i < arr.length; i += 4) {
      System.arraycopy(arr, i, tmp, 0, 4);
      out[i / 4] = bigEndianByteToInt(tmp);
    }
    return out;
  }

  /**
   * @Description:
   * @ReturnType：byte[]
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  private static byte[] convert(int[] arr) {
    byte[] out = new byte[arr.length * 4];
    byte[] tmp = null;
    for (int i = 0; i < arr.length; i++) {
      tmp = bigEndianIntToByte(arr[i]);
      System.arraycopy(tmp, 0, out, i * 4, 4);
    }
    return out;
  }

  /**
   * @Description:
   * @ReturnType：int[]
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  public static int[] CF(int[] V, int[] B) {
    int a, b, c, d, e, f, g, h;
    int ss1, ss2, tt1, tt2;
    a = V[0];
    b = V[1];
    c = V[2];
    d = V[3];
    e = V[4];
    f = V[5];
    g = V[6];
    h = V[7];

    /*
     * System.out.println("IV: ");
     * System.out.print(Integer.toHexString(a)+" ");
     * System.out.print(Integer.toHexString(b)+" ");
     * System.out.print(Integer.toHexString(c)+" ");
     * System.out.print(Integer.toHexString(d)+" ");
     * System.out.print(Integer.toHexString(e)+" ");
     * System.out.print(Integer.toHexString(f)+" ");
     * System.out.print(Integer.toHexString(g)+" ");
     * System.out.print(Integer.toHexString(h)+" "); System.out.println("");
     * System.out.println("");
     *
     * System.out.println("填充后的消息: "); for(int i=0; i<B.length; i++) {
     * System.out.print(Integer.toHexString(B[i])+" "); }
     * System.out.println(""); System.out.println("");
     */

    int[][] arr = expand(B);
    int[] w = arr[0];
    int[] w1 = arr[1];

    /*
     * System.out.println("扩展后的消息： "); System.out.println("W0W1...W67");
     * print(w); System.out.println("");
     * System.out.println("W'0W'1...W'67"); print(w1);
     * System.out.println("迭代压缩中间值: ");
     */

    for (int j = 0; j < 64; j++) {
      ss1 = (bitCycleLeft(a, 12) + e + bitCycleLeft(Tj[j], j));
      ss1 = bitCycleLeft(ss1, 7);
      ss2 = ss1 ^ bitCycleLeft(a, 12);
      tt1 = FFj(a, b, c, j) + d + ss2 + w1[j];
      tt2 = GGj(e, f, g, j) + h + ss1 + w[j];
      d = c;
      c = bitCycleLeft(b, 9);
      b = a;
      a = tt1;
      h = g;
      g = bitCycleLeft(f, 19);
      f = e;
      e = P0(tt2);

      /*
       * System.out.print(j+" ");
       * System.out.print(Integer.toHexString(a)+" ");
       * System.out.print(Integer.toHexString(b)+" ");
       * System.out.print(Integer.toHexString(c)+" ");
       * System.out.print(Integer.toHexString(d)+" ");
       * System.out.print(Integer.toHexString(e)+" ");
       * System.out.print(Integer.toHexString(f)+" ");
       * System.out.print(Integer.toHexString(g)+" ");
       * System.out.print(Integer.toHexString(h)+" ");
       * System.out.println("");
       */
    }
    // System.out.println("");

    int[] out = new int[8];
    out[0] = a ^ V[0];
    out[1] = b ^ V[1];
    out[2] = c ^ V[2];
    out[3] = d ^ V[3];
    out[4] = e ^ V[4];
    out[5] = f ^ V[5];
    out[6] = g ^ V[6];
    out[7] = h ^ V[7];

    return out;
  }

  /**
   * @Description:
   * @ReturnType：int[][]
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  private static int[][] expand(int[] B) {
    int W[] = new int[68];
    int W1[] = new int[64];
    for (int i = 0; i < B.length; i++) {
      W[i] = B[i];
    }

    for (int i = 16; i < 68; i++) {
      W[i] = P1(W[i - 16] ^ W[i - 9] ^ bitCycleLeft(W[i - 3], 15))
          ^ bitCycleLeft(W[i - 13], 7) ^ W[i - 6];
    }

    for (int i = 0; i < 64; i++) {
      W1[i] = W[i] ^ W[i + 4];
    }

    int arr[][] = new int[][]{W, W1};
    return arr;
  }

  /**
   * @Description:
   * @ReturnType：byte[]
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  private static byte[] bigEndianIntToByte(int num) {
    return back(Util.intToBytes(num));
  }

  /**
   * @Description:
   * @ReturnType：int
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  private static int bigEndianByteToInt(byte[] bytes) {
    return Util.byteToInt(back(bytes));
  }

  /**
   * @Description:
   * @ReturnType：int
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  private static int FFj(int X, int Y, int Z, int j) {
    if (j >= 0 && j <= 15) {
      return FF1j(X, Y, Z);
    } else {
      return FF2j(X, Y, Z);
    }
  }

  /**
   * @Description:
   * @ReturnType：int
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  private static int GGj(int X, int Y, int Z, int j) {
    if (j >= 0 && j <= 15) {
      return GG1j(X, Y, Z);
    } else {
      return GG2j(X, Y, Z);
    }
  }

  /**
   * @Description:逻辑位运算函数
   * @ReturnType：int
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  private static int FF1j(int X, int Y, int Z) {
    int tmp = X ^ Y ^ Z;
    return tmp;
  }

  /**
   * @Description:逻辑位运算函数
   * @ReturnType：int
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  private static int FF2j(int X, int Y, int Z) {
    int tmp = ((X & Y) | (X & Z) | (Y & Z));
    return tmp;
  }

  /**
   * @Description:逻辑位运算函数
   * @ReturnType：int
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  private static int GG1j(int X, int Y, int Z) {
    int tmp = X ^ Y ^ Z;
    return tmp;
  }

  /**
   * @Description:逻辑位运算函数
   * @ReturnType：int
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  private static int GG2j(int X, int Y, int Z) {
    int tmp = (X & Y) | (~X & Z);
    return tmp;
  }

  /**
   * @Description:
   * @ReturnType：int
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  private static int P0(int X) {
    int y = rotateLeft(X, 9);
    y = bitCycleLeft(X, 9);
    int z = rotateLeft(X, 17);
    z = bitCycleLeft(X, 17);
    int t = X ^ y ^ z;
    return t;
  }

  /**
   * @Description:
   * @ReturnType：int
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  private static int P1(int X) {
    int t = X ^ bitCycleLeft(X, 15) ^ bitCycleLeft(X, 23);
    return t;
  }

  /**
   * @Description:对最后一个分组字节数据padding
   * @ReturnType：byte[]
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  public static byte[] padding(byte[] in, int bLen) {
    int k = 448 - (8 * in.length + 1) % 512;
    if (k < 0) {
      k = 960 - (8 * in.length + 1) % 512;
    }
    k += 1;
    byte[] padd = new byte[k / 8];
    padd[0] = (byte) 0x80;
    long n = in.length * 8 + bLen * 512;
    byte[] out = new byte[in.length + k / 8 + 64 / 8];
    int pos = 0;
    System.arraycopy(in, 0, out, 0, in.length);
    pos += in.length;
    System.arraycopy(padd, 0, out, pos, padd.length);
    pos += padd.length;
    byte[] tmp = back(Util.longToBytes(n));
    System.arraycopy(tmp, 0, out, pos, tmp.length);
    return out;
  }

  /**
   * @Description:字节数组逆序
   * @ReturnType：byte[]
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  private static byte[] back(byte[] in) {
    byte[] out = new byte[in.length];
    for (int i = 0; i < out.length; i++) {
      out[i] = in[out.length - i - 1];
    }

    return out;
  }

  /**
   * @Description:
   * @ReturnType：int
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  public static int rotateLeft(int x, int n) {
    return (x << n) | (x >> (32 - n));
  }

  /**
   * @Description:
   * @ReturnType：int
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  private static int bitCycleLeft(int n, int bitLen) {
    bitLen %= 32;
    byte[] tmp = bigEndianIntToByte(n);
    int byteLen = bitLen / 8;
    int len = bitLen % 8;
    if (byteLen > 0) {
      tmp = byteCycleLeft(tmp, byteLen);
    }

    if (len > 0) {
      tmp = bitSmall8CycleLeft(tmp, len);
    }

    return bigEndianByteToInt(tmp);
  }

  /**
   * @Description:
   * @ReturnType：byte[]
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  private static byte[] bitSmall8CycleLeft(byte[] in, int len) {
    byte[] tmp = new byte[in.length];
    int t1, t2, t3;
    for (int i = 0; i < tmp.length; i++) {
      t1 = (byte) ((in[i] & 0x000000ff) << len);
      t2 = (byte) ((in[(i + 1) % tmp.length] & 0x000000ff) >> (8 - len));
      t3 = (byte) (t1 | t2);
      tmp[i] = (byte) t3;
    }

    return tmp;
  }

  /**
   * @Description:
   * @ReturnType： byte[]
   * @Since:2016年9月13日 17:25:00
   * @Author: guoleijie@shie.com.cn
   */
  private static byte[] byteCycleLeft(byte[] in, int byteLen) {
    byte[] tmp = new byte[in.length];
    System.arraycopy(in, byteLen, tmp, 0, in.length - byteLen);
    System.arraycopy(in, 0, tmp, in.length - byteLen, byteLen);
    return tmp;
  }

  /*
   * private static void print(int[] arr) { for (int i = 0; i < arr.length;
   * i++) { System.out.print(Integer.toHexString(arr[i]) + " "); if ((i + 1) %
   * 16 == 0) { System.out.println(); } } System.out.println(); }
   */
}
